package fr.emse.tatiana.remotecontrol;



import java.util.List;
import java.util.Vector;

import org.eclipse.swt.*;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.PlatformUI;

import fr.emse.tatiana.replayable.Anchor;
import fr.emse.tatiana.replayable.DiscreteReplayable;
import fr.emse.tatiana.replayable.SynchronisationEvent;

public class RemoteWidgetSWT implements DiscreteReplayable{
	// viewable window dimensions
	int width = 350;
	int height = 300;

	// times to show on the time scale
	long timeMin;
	long timeMax;

	// position of playback bar
	int nowPosition;
	
	// are we playing the current selection?
	boolean selectionplay = false;

	// position of where mouse button was clicked
	int selectedMouseMin;
	int selectedMouseMax;

	// where to place the scale in the canvas
	// (not happy about this comment/definition)
	// xMin: start of scale from left of shell window
	// xMax: end of scale from left of shell window
	// y: distance of the scale from the top of shell window
	int xMin = 50;
	int yMin = 100;
	int xMax = 300;
	int yMax = 100; // never really used I think since it's equal to yMin

	// scale variables
	int scale = 8; // scale "zoom" level // means you have 'scale' bars in a view
	int selectionSize = 20; // height of the selected area
	int smallScaleHeight = 4; // height of small scale bar
	int bigScaleHeight = 16; // height of big scale bar
	int scaleNumbersHeight = 30; // height at which values on scale should be put

	// keeps track of if mouse button is pressed
	boolean isMouseDown = false;

	// colors
	int selectionColor=SWT.COLOR_BLUE; // color of the selection objects
	int scaleColor=SWT.COLOR_RED; // color of the scale bars
	int scaleColor2=SWT.COLOR_DARK_RED; // 2nd color for scale bars
	int bgColor=SWT.COLOR_WHITE; // background color
	int nowColor=SWT.COLOR_YELLOW; // playback bar color

	// high level objects that we need to access
	Shell shell;
	Display display;
	Canvas canvas;

	// scale view objects
	Button zoomInButton;
	Button zoomOutButton;
	ScrollBar hBar; // scale's horizontal scrollbar

	// remote control variables/objects
	RemoteControl rc; // the associated remote control
	private long startTime = 0;
	private long durationTime = 0;
	Label speedLabel;
	Label timeLabel;
	Button playButton;
	Button playselButton;
	Button speedSlowerButton;
	Button speedFasterButton;
	private Button checkbox;

	// Constructor
	// associates remote control and widget together
	// then initialises widget
	public RemoteWidgetSWT(RemoteControl rc) {
		this.rc=rc;
		rc.setWidget(this);
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				initGUI();
			}
		});
	}

	// initializes the widget graphical interface
	// initializes mouse & buttons listeners
	public void initGUI() {
		// init window, and graphical object
		shell = new Shell(PlatformUI.getWorkbench().getDisplay(),SWT.EMBEDDED | SWT.MODELESS | SWT.CLOSE | SWT.RESIZE);
		// set window title
		shell.setText("Remote Control");

		// init layout for the shell
		FormLayout layout= new FormLayout ();
		layout.marginHeight = 5;
		layout.marginWidth = 5;
		shell.setLayout(layout);

		// define point of origin, needed to convert cordinates once the scrollbar is used
		final Point origin = new Point (0, 0);

		// init canvas (where we are going to draw the scale)
		canvas = new Canvas (shell, SWT.NO_BACKGROUND |	SWT.NO_REDRAW_RESIZE | SWT.H_SCROLL);
		// define canvas layout
		FormData formData = new FormData(width,height/2);
		formData.left = new FormAttachment(0,5);
		formData.right = new FormAttachment(100,-5);
		formData.bottom = new FormAttachment(50,0);
		canvas.setLayoutData(formData);

		// define buttons (and there layout) to add to the shell
		zoomInButton = new Button(shell, SWT.PUSH);
		formData = new FormData();
		formData.top = new FormAttachment(50,5);
		zoomInButton.setLayoutData(formData);
		//zoomInButton.setBounds(10, 10, 50, 30);
		zoomInButton.setText("Zoom In");

		zoomOutButton = new Button(shell, SWT.PUSH);
		formData = new FormData();
		formData.top = new FormAttachment(50,5);
		formData.left = new FormAttachment(50,0);
		zoomOutButton.setLayoutData(formData);
		zoomOutButton.setText("Zoom Out");

		playButton = new Button(shell, SWT.PUSH);
		playselButton = new Button(shell, SWT.PUSH);
		formData = new FormData();
		formData.top = new FormAttachment(60,5);
		formData.left = new FormAttachment(50,0);
		playButton.setLayoutData(formData);
		formData = new FormData();
		formData.top = new FormAttachment(60,5);
		playselButton.setLayoutData(formData);
		playselButton.setText("Play selection");
		playButton.setText(" Play "); // (extra spaces added so button is properly sized for when text is changed to 'Pause'

		speedFasterButton = new Button(shell,SWT.PUSH);
		formData = new FormData();
		formData.top = new FormAttachment(70,5);
		formData.left = new FormAttachment(50,0);
		speedFasterButton.setLayoutData(formData);
		speedFasterButton.setText("Faster");

		speedSlowerButton = new Button(shell,SWT.PUSH);
		formData = new FormData();
		formData.top = new FormAttachment(70,5);
		speedSlowerButton.setLayoutData(formData);
		speedSlowerButton.setText("Slower");

		speedLabel = new Label(shell, SWT.NONE);
		formData = new FormData();
		formData.top = new FormAttachment(70,5);
		formData.left = new FormAttachment(25,0);
		speedLabel.setLayoutData(formData);
		speedLabel.setText(" 1 ");

		timeLabel = new Label(shell, SWT.NONE);
		formData = new FormData();
		formData.top = new FormAttachment(60,5);
		formData.left = new FormAttachment(75,0);
		timeLabel.setLayoutData(formData);
		timeLabel.setText(" 0:00 ");
		
		checkbox = new Button(shell,SWT.CHECK);
		formData = new FormData();
		formData.top = new FormAttachment(70,5);
		formData.left = new FormAttachment(75,0);
		checkbox.setLayoutData(formData);
		checkbox.setText("ID synch only");

		// define button listener, and what actions to do when a button is pressed
		Listener buttonListener = new Listener() {
			public void handleEvent(Event event) {
				if (event.widget == zoomInButton) {
					// change scale
					scale *= 2;
					// move xMax to new position
					xMax = xMax*2-xMin;
					// move selection to new scaled position
					selectedMouseMin = selectedMouseMin*2-xMin;
					selectedMouseMax = selectedMouseMax*2-xMin;
					// enable scroll bar to scroll more
					hBar.setMaximum(xMax-2*xMin);
					canvas.redraw();
				} else if (event.widget == zoomOutButton && scale>1) {
					// change scale
					scale /=2;
					// move xMax to new position
					xMax = (xMax+xMin)/2;
					// move selection to new scaled position
					selectedMouseMin = (selectedMouseMin+xMin)/2;
					selectedMouseMax = (selectedMouseMax+xMin)/2;
					// resize scroll bar to scroll less
					hBar.setMaximum(xMax-2*xMin);
					canvas.redraw();
				} else if (event.widget == playButton ) {
					// notify remote control
					selectionplay = false;
					if (rc.isPaused()) {
						rc.play();
					} else {
						rc.pause();    		
					}
				} else if (event.widget == playselButton ) {
					// notify remote control
					nowPosition = selectedMouseMin;
					sendGoToEvent(); // rewind
					selectionplay = true;
					if (rc.isPaused()) {
						rc.play();
					}
				}else if (event.widget == speedFasterButton ) {
					// notify remote control
					rc.setSpeed(rc.getSpeed()*2);
				} else if (event.widget == speedSlowerButton && rc.getSpeed() > 1) { 
					// notify remote control
					rc.setSpeed(rc.getSpeed()/2);
				} else if (event.widget == checkbox) {
					rc.setIDSynch(checkbox.getSelection());
				}
			}
		};

		// add listner to all buttons
		zoomInButton.addListener(SWT.Selection, buttonListener);
		zoomOutButton.addListener(SWT.Selection, buttonListener);
		playButton.addListener(SWT.Selection, buttonListener);
		playselButton.addListener(SWT.Selection, buttonListener);
		speedSlowerButton.addListener(SWT.Selection, buttonListener);
		speedFasterButton.addListener(SWT.Selection, buttonListener);
		checkbox.addListener(SWT.Selection, buttonListener);

		// define canvas's horizontal scroll bar and corresponding listener
		hBar = canvas.getHorizontalBar ();
		hBar.addListener (SWT.Selection, new Listener () {
			public void handleEvent (Event e) {
				int hSelection = hBar.getSelection ();
				int destX = -hSelection - origin.x;
				Rectangle rect = canvas.getBounds ();
				canvas.scroll (destX, 0, 0, 0, rect.width, rect.height, false);
				origin.x = -hSelection;
				canvas.redraw();
			}
		});

		// add resize listener to canvas
		canvas.addListener (SWT.Resize,  new Listener () {
			public void handleEvent (Event e) {
				canvas.redraw ();
			}
		});

		// define paint listener for canvas
		canvas.addListener (SWT.Paint, new Listener () {
			public void handleEvent (Event e) {
				// get graphique context
				GC gc = e.gc;

				// draw background
				gc.setBackground(e.display.getSystemColor(bgColor));
				Rectangle rect = canvas.getBounds ();
				Rectangle client = canvas.getClientArea ();
				gc.fillRectangle(client);

				// paint selection (selected interval)
				// if no interval draw line
//				if(selectedMouseMax == selectedMouseMin) {
//				gc.setForeground(e.display.getSystemColor(selectionColor));
//				gc.drawLine(selectedMouseMin+origin.x,yMin-selectionSize,selectedMouseMin+origin.x,yMin+selectionSize);
//				}else{
				// draw rectangle
				gc.setBackground(e.display.getSystemColor(selectionColor));
				gc.fillRectangle(selectedMouseMin+origin.x, yMin-selectionSize/2+origin.y, selectedMouseMax-selectedMouseMin, selectionSize);
//				}

				// paint 'now' position (playback position)
				gc.setForeground(e.display.getSystemColor(nowColor));
				gc.drawLine(nowPosition+origin.x, yMin-50, nowPosition+origin.x, yMin+50);

				// paint scale lines
				gc.setForeground(e.display.getSystemColor(scaleColor));
				// draw horizontal scale line
				gc.drawLine(xMin+origin.x, yMin+origin.y, xMax+origin.x, yMax+origin.y);
				// draw vertical scale lines
				// define number of small scales
				int numberOfIntermediateScales = scale;
				// draw small scales
				for( int i=0 ; i<=numberOfIntermediateScales ; i++ ) {
					gc.drawLine(origin.x+xMin+(int)((float)(xMax-xMin)/(float)numberOfIntermediateScales*i), origin.y+yMin-smallScaleHeight/2, origin.x+xMin+(int)((float)(xMax-xMin)/(float)numberOfIntermediateScales*i), origin.y+yMin+smallScaleHeight/2);
				}
				// define number of big scales
				int numberOfIntermediateBigScales = numberOfIntermediateScales/2;
				// draw big scales
				for( int i=0 ; i<numberOfIntermediateBigScales ; i++ ) {
					gc.drawLine(origin.x+xMin+(int)((float)(xMax-xMin)/(float)numberOfIntermediateBigScales*i), origin.y+yMin-bigScaleHeight/2, origin.x+xMin+(int)((float)(xMax-xMin)/(float)numberOfIntermediateBigScales*i), origin.y+yMin+bigScaleHeight/2);
				}

				// paint scale numbers
				gc.setBackground(e.display.getSystemColor(bgColor));
				// convert timestamps to MM:SS format
				String tMin = convertMilliseconds2MMSS(timeMin-timeMin);
				String tMax = convertMilliseconds2MMSS(timeMax-timeMin);
				// calculate size of times on screen (to center them later)
				int tMinSize = gc.textExtent(tMin).x;
				int tMaxSize = gc.textExtent(tMax).x;
				// draw numbers on screen
				// drew begining and end of scale
				gc.drawText(tMin, xMin+origin.x-tMinSize/2, yMin-scaleNumbersHeight+origin.y);
				gc.drawText(tMax, xMax+origin.x-tMaxSize/2, yMin-scaleNumbersHeight+origin.y);
				// draw intermediate numbers
				for( int i=0 ; i<numberOfIntermediateScales-1 ; i++ ) {
					// change color ever number for better readability
					if( i%2 == 0 ) {
						gc.setForeground(e.display.getSystemColor(scaleColor2));
					} else {
						gc.setForeground(e.display.getSystemColor(scaleColor));
					}
					// convert time to MM:SS format
					String timeI = convertMilliseconds2MMSS((long)((double)(timeMax-timeMin)/(double)(numberOfIntermediateScales)*(i+1)));
					// calculate size of text on screen
					int timeISize = gc.textExtent(timeI).x;
					// draw number
					gc.drawText(timeI, xMin+origin.x-(int)((float)timeISize/(float)2)+(int)((float)(xMax-xMin)/(float)(numberOfIntermediateScales)*(i+1)), yMin-scaleNumbersHeight+origin.y-15*((i+1)%2));
				}
			}
		});

		// define mouse adapter, and what happens when mouse buttons are pressed
		MouseAdapter mouseAdapter = new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				// set selection minimum to current mouse coordinates
				// and correct with scroll position
				selectedMouseMin = e.x-origin.x;
				isMouseDown = true;
			}
			@Override
			public void mouseUp(MouseEvent e) {
				// set selection maximum to current mouse coordinates
				// and correct with scroll position
				selectedMouseMax = e.x-origin.x;
				isMouseDown = false;
				// swap min and max if they where defined in wrong order
				correctMinMax();
				// set the playback position
				nowPosition = selectedMouseMin;
				canvas.redraw();
				// notify remote control
				sendGoToEvent();
			}
		};
		// add mouse button listnener to canvas
		canvas.addMouseListener(mouseAdapter);

		// mouse wheel listener (maybe for later, to scroll scroll bar when wheel is turned
		canvas.addListener(SWT.MouseWheel, new Listener() {
			public void handleEvent(Event event) {
				if( event.count > 0) { // mouse wheel up
				}
				if( event.count < 0 ) { // mouse wheel down
				}
			}
		});

		// define mouse move listener
		MouseMoveListener mouseMove = new MouseMoveListener() {
			public void mouseMove(MouseEvent e) {
				if(isMouseDown) {
					selectedMouseMax = e.x-origin.x;
					if( e.x > canvas.getBounds().width ) {
						// the idea would be to scroll when mouse selection goes off screen
						// haven't found out how to do that yet (without messing the rest up ;)
					}
					canvas.redraw();
					// notify remote control
					sendGoToEvent();	
				}
			}
		};
		canvas.addMouseMoveListener(mouseMove);

		// define what to do when shell is closed 
		shell.addShellListener(new ShellListener() {
			public void shellActivated(ShellEvent e) {				
			}
			public void shellClosed(ShellEvent e) {
				rc.end(); // tell remote control to end
			}
			public void shellDeactivated(ShellEvent e) {				
			}
			public void shellDeiconified(ShellEvent e) {				
			}
			public void shellIconified(ShellEvent e) {				
			}      	
		});		

		// define default selection
		selectedMouseMin = xMin;
		selectedMouseMax = xMin;
		// define default playback position
		nowPosition = xMin;

		// finish initializing shell
		shell.setSize (width, height);
		shell.open ();
	}

	// notifies remote control of current selection
	protected void sendGoToEvent() {
		// get selection min max
		long t1 = getSelectedMaxAsTimestamp();
		long t2 = getSelectedMinAsTimestamp();
		// order them if needed (a call to correctMinMax would to the trick too)
		if (t1 > t2) {
			long tmp = t1;
			t1 = t2;
			t2 = tmp;
		}
		// notify remote control
		SynchronisationEvent se = new SynchronisationEvent(this, t1, t1 ,t2-t1);
		se.addAnchor(new Anchor("<begin>"+t1+"</begin><end>"+t2+"</end>"));
		rc.goTo(se);
	}

	// sets minimum to real minimum if below scale
	// sets maximum to real maximum if above scale
	// swaps min/max if max < min
	void correctMinMax() {
		if ( selectedMouseMax < selectedMouseMin ) {
			int tmp = selectedMouseMax;
			selectedMouseMax = selectedMouseMin;
			selectedMouseMin = tmp;
		}
		if( selectedMouseMin < xMin )
			selectedMouseMin = xMin;
		if( selectedMouseMax > xMax )
			selectedMouseMax = xMax;
		// the following situations can happen if selection is done left of scale
		if( selectedMouseMax < xMin )
			selectedMouseMax = xMin;
		// or right of scale
		if( selectedMouseMin > xMax )
			selectedMouseMin = xMax;
	}


	// Graphically selectes the interval described by 2 timestamps
	// What is does: calculates at what percentage of the scale
	// the selectedMouseMin & selected MouseMax are
	// then applies the same percentages to the [minTime,maxTime] interval
	// See function convertMouseCoodinates2Timestamp for more explicite variable names
	// ConvertMouseCoodinates2Timestamp is the reverse function to this one
	// Hopefully selectInterval(convertMouseCoodinates2Timestamp(X),convertMouseCoodinates2Timestamp(Y))
	// selects interval [X,Y] (probably some rounding errors)
	private int timeToCoords(long a) {
		float s = timeMax-timeMin;
		float t = a - timeMin;
		t = t*100;
		t = t/s;
		float delta = (float)xMax-(float)xMin;
		delta = delta*t;
		delta = delta / 100;
		return (int)delta+xMin;
	}

	// selectes interval defined by 2 timestamps
	private void selectInterval(long a, long b) {
		selectedMouseMin = timeToCoords(a);
		selectedMouseMax = timeToCoords(b);
	}

	// updates 'now' position (playback position)
	public void setNowPosition(long l) {
		nowPosition = timeToCoords(l);
	}


	public void goTo(SynchronisationEvent extract) {
		if (extract.getNow()==null) {
			return;
		}
		final long l = extract.getNow() - startTime;
		final boolean updatevisible = (extract.getSource()!=this);

		long begin;
		long end;
		if (extract.getDuration()!=0) {
			begin = extract.getBegin();
			end = extract.getBegin() + extract.getDuration();
		} else {
			begin = end = extract.getNow();
			// update now position (playback position)
			setNowPosition(extract.getNow());
			// stop play back is we are at the end of the selection
			// then rewind to beginning of selection
			if(selectionplay && (nowPosition > selectedMouseMax)) {
				System.err.println("now>max: pausing");
				rc.pause();
				return;
			}
	// TODO FIX THIS (stop playback when at end of selection)	
		}

		final long b = begin;
		final long e = end;

		final boolean isPlaying = !rc.isPaused();

		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				updateTimeLabel(l);
				if (updatevisible) {
					if(!isPlaying) {
						selectInterval(b,e);
						nowPosition = timeToCoords(b);
					}
				}	
				canvas.redraw();
			}
		});
	}

	public void end() {
		shell.setVisible(false);
		//shell.dispose();
	}

	public List<Long> getEvents() {
		// TODO figure out long intervals where nothing happens
		List<Long> ret = new Vector<Long>();
		if (durationTime > 3*3600*1000) { // three hours should be enough 
			                               // for anybody
			System.err.println("long duration");
			for (int i=0;i<1000;i++) {
				ret.add(new Long(startTime+(i*durationTime/1000))); // 1000 ticks
			}
		} else if(durationTime<100*1000) { // shorter than number of ticks
			for (int i=0;i<400;i++) {
				ret.add(new Long(startTime+(i*durationTime/400))); // 400 ticks
			}
		} else {
			for (int i=0;i<=durationTime/1000;i++) {
				ret.add(new Long(startTime+i*1000)); // tick every second
			}
		}
		return ret;
	}

	public void mark(long time) {

	}

	public void setBounds(long startTime, long endTime) {
		this.startTime=startTime;
		this.durationTime=endTime-startTime;
		timeMin = startTime;
		timeMax = endTime;
	}

	public void showSpeed(final int speed) {
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				speedLabel.setText(""+speed);
			}
		});
	}

	public void showPaused() {
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				playButton.setText("Play");
			}
		});
	}

	public void showPlaying() {
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				playButton.setText("Pause");
			}
		});
	}

	protected void updateTimeLabel(long timemilli) { 	
		timeLabel.setText(convertMilliseconds2MMSS(timemilli));
	}

	// converts a timestamp in millisecondes to MM:SS format
	String convertMilliseconds2MMSS(long milliseconds) {  	
		long timeminutes = milliseconds/60000;
		long timeseconds = milliseconds/1000 - timeminutes*60;
		String sec = timeseconds<10?"0"+timeseconds:""+timeseconds;
		String min = ""+timeminutes;
		return(min+":"+sec);
	}

	// converts mouse shell coordinates (from the scale)
	// to the equivalent timestamp
	// (if mouse is at 25% of the scale, then returns, 25% of the time between timeMin and timeMax)
	long convertMouseCoodinates2Timestamp(int mousePosition ) {
		// calculate length of possible mouse coordinates interval
		float length = (float)xMax - (float)xMin;
		// calculate what percentage of the interval mousePosition is
		float percentage = (float)mousePosition - (float)xMin;
		percentage = percentage * 100;
		percentage = percentage / length;
		// apply same percentage to time interval [timeMin;timeMax]
		float result = (timeMax-timeMin);
		result = result*percentage;
		result = result / 100;
		return ((long)(result+(double)timeMin));
	}

	long getSelectedMaxAsTimestamp() {
		return(convertMouseCoodinates2Timestamp(selectedMouseMax));
	}
	long getSelectedMinAsTimestamp() {
		return(convertMouseCoodinates2Timestamp(selectedMouseMin));
	}



} 

